/*
 * Copyright (C) 2010 Johan Waldeback
 *  
 * This file is part of the OpenFMS project (http://www.openfms-project.org)
 * 
 * OpenFMS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "ModelSimulator.h"

#ifdef WIN32
    #include <windows.h>
#endif

#include <stdio.h>
#include <tchar.h>
#include <cassert>


using namespace OpenFms;

/**
 * ModelSimulator constructor
 */
ModelSimulator::ModelSimulator()
{
    this->resetAllMembers();
    setErrorMessage(NULL);
}

/**
 * ModelSimulator destructor
 */
ModelSimulator::~ModelSimulator()
{
    this->destroy();
}

/**
 * Resets instance variables
 */
void ModelSimulator::resetAllMembers()
{
    // clear everything ... except the error message !!!
    TCHAR buffer[ModelSimulator_ErrorMessage_BufferSize];
    memcpy(buffer, this->m_errorMessage, ModelSimulator_ErrorMessage_BufferSize*sizeof(TCHAR));
    memset(this,0, sizeof(*this));
    memcpy(this->m_errorMessage, buffer, ModelSimulator_ErrorMessage_BufferSize*sizeof(TCHAR));
}

/**
 * Returns a string describing the last error
 *
 * @return Returns the latest error message
 */
const TCHAR *ModelSimulator::getLastErrorMessage()
{
    return this->m_errorMessage;
}

/**
 * Set error message
 *
 * @param pMessage The message to set.
 */
void ModelSimulator::setErrorMessage(const TCHAR *pMessage)
{
    if(pMessage == NULL ) {
        assert( ModelSimulator_ErrorMessage_BufferSize > 10 && "No-error message to long for buffer" );
        _tcscpy(this->m_errorMessage, _T("No error"));
    }
    else if(_tcslen(pMessage) < ModelSimulator_ErrorMessage_BufferSize ) {
        _tcscpy(this->m_errorMessage, pMessage);
    }
    else {
        assert( ModelSimulator_ErrorMessage_BufferSize > 50 && "Error message to long for buffer" );
        _tcscpy(this->m_errorMessage, _T("<Error message to long for error message buffer>"));
    }
}

/**
 * Load functions from a dynamic library
 * 
 * Loads a dynamic library, and set the instance functions pointers
 * to pointe the the functions in the loaded library.
 *
 * @param pModuleName Filename of the dynamic library file to load
 * @return Returns true on success, false on fail.
 */
bool ModelSimulator::loadFunctions(const TCHAR *pModuleName)
{
	// set status message
    this->setErrorMessage(_T("ModelSimulator::loadFunctions - ENTER"));

	// check input
    if(!pModuleName) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions - Module name is NULL"));
        goto return_failure;
    }

#ifdef WIN32
    // WIN32: Load the library
    m_hDllFile = LoadLibrary(pModuleName);
    if( m_hDllFile == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to load library"));
        goto return_failure;
    }

    // WIN32: Load function "create"
    this->m_create_fn = (ModelSimulator_create_t)GetProcAddress(m_hDllFile, "create");
    if(this->m_create_fn == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to find function: create"));
        goto return_failure;
    }

    // WIN32: Load function "detroy"
    this->m_destroy_fn = (ModelSimulator_destroy_t)GetProcAddress(m_hDllFile, "destroy");
    if(this->m_destroy_fn == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to find function: destroy"));
        goto return_failure;
    }

    // WIN32: Load function "getinertias"
    this->m_getinertias_fn = (ModelSimulator_getinertias_t)GetProcAddress(m_hDllFile, "getinertias");
    if(this->m_getinertias_fn == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to find function: getinertias"));
        goto return_failure;
    }

    // WIN32: Load function "setstepsize"
    this->m_setstepsize_fn = (ModelSimulator_setstepsize_t)GetProcAddress(m_hDllFile, "setstepsize");
    if(this->m_setstepsize_fn == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to find function: setstepsize"));
        goto return_failure;
    }

    // WIN32: Load function "step"
    this->m_step_fn = (ModelSimulator_step_t)GetProcAddress(m_hDllFile, "step");
    if(this->m_step_fn == NULL ) {
        this->setErrorMessage(_T("ModelSimulator::loadFunctions (WIN32) - Unable to find function: step"));
        goto return_failure;
    }

#else // assume linux
    // todo: generate compiler error here.
    assert( false && "linux not yet implanted" );
    this->setErrorMessage(_T("ModelSimulator::loadFunctions (non-WIN32) - Functionality not yet implanted for this platform"));
    return false;
#endif

    // if here, then success
    return true;
return_failure:
    // if here, then failue
    this->destroy(); // cleanup before exiting.
    return false;
}

/**
 * Calls the \c create function in the currentrly loaded library.
 * @param read_fn Pointer to callback reader function passed on to \c create.
 * @param custom Custom value passed on to \c cretate
 * @return true if \c create returns with success, false otherwise.
 */
bool ModelSimulator::create(ModelSimulator_read_callback_t read_fn, uint64_t custom)
{
    // check _create function is loaded
    if(this->m_create_fn == NULL) {
        this->setErrorMessage(_T("ModelSimulator::create - DL create function not loaded"));
        return false;
    }

    // create using _create
    this->m_handle = this->m_create_fn(read_fn, custom );
    if( this->m_handle == 0L ) {
        this->setErrorMessage(_T("ModelSimulator::create - DL create function failed"));
        return false;
    }

	// if here, then success
    return true;
}

/**
 * Convinience function that first calls \c loadFunctions and then \c create.
 *
 * @param pFileName File name of dynamic library to load
 * @param read_fn Pointer to callback reader function.
 * @param custom Custom parameter, this parrameter is uses when calling the read_fn callback function
 * @return true if successful, otherwise false.
 */
bool ModelSimulator::load(const TCHAR *pFileName, ModelSimulator_read_callback_t read_fn, uint64_t custom)
{
    // Load functions
    // note: loadFunctions will set the error message on fail
    this->setErrorMessage(_T("ModelSimulator::load (from callback) - calling loadFunctions"));
    if(!this->loadFunctions(pFileName)) {
        //this->setErrorMessage(_T("ModelSimulator::load (from callback) - loadFunctions failed"));
        return false;
    }

	// create handle object
    // note: create will set the error message on fail
    this->setErrorMessage(_T("ModelSimulator::load (from callback) - calling create ... "));
	if(!this->create(read_fn, custom)) {
        //this->setErrorMessage(_T("ModelSimulator::load (from callback) - loadFunctions failed"));
        return false;
    }

    // if here, then success
    return true;
}

/** 
 * Callback function used when reading from a file
 *
 * This function is used as the callback function when using ModelSimulator::load function
 * when the content of a file is to be loaded.
 *
 * @param[in] _file File handle from a call to fopen casted to a uint64_t.
 * @param[in,out] _buffer Pointer to the buffer to receive the read data, casted to an uint64_t.
 * @param[in] _buffer_size Maximum number of bytes to read.
 * @return Returns the number of bytes read from the file.
 */
static uint64_t __cdecl read_from_file(uint64_t _file, uint64_t _buffer, uint64_t _buffer_size)
{
	// TODO: test this one
	// check it handles end of files well

	// retrun 0 if EOF has been reached
    if(feof(reinterpret_cast<FILE *>(_file))) return 0;

    // use fread to read from the file
    return static_cast<uint64_t>( fread(
        reinterpret_cast<void *>(_buffer),
        1,
        static_cast<size_t>(_buffer_size),
        reinterpret_cast<FILE*>(_file ) ));
}

/**
 * Helper function that first loads a dynamic library and then calls create.
 *
 * Convinience function that:
 *  -# Calls \c loadFunctions
 *  -# Opens the named file, and sets up a callback function used to read from the file
 *  -# Calls \c create using that callback function
 *
 * @param pModuleName File name of dynamic library to load
 * @param pFileName File name of data file to read from.
 * @return true if successful, otherwise false.
 */
bool ModelSimulator::load(const TCHAR *pModuleName, const TCHAR *pFileName)
{
    // open the file
    FILE *file = _tfopen(pFileName, _T("rb"));
    if(!file) {
        this->setErrorMessage(_T("ModelSimulator::load (from file) - Unable to open data file"));
        return false;
    }

    // load from the file
    // note: the function will set error message on fail
    bool result = load(pModuleName, read_from_file, reinterpret_cast<uint64_t>(file));

    // close the file
    fclose(file);

    // return the result from the creation
    return result;
}

/**
 * Unloads a loaded library, after hanving called the \c destroy in that library.
 *
 * @note Does not clear/set the error message, since this might overwrite
 * messages from other functions that failed and now calling destroy 
 * to cleanup after itself.
 */
void ModelSimulator::destroy()
{
// never fails
    // destroy the object
    if(this->m_destroy_fn && this->m_handle ) {
        this->m_destroy_fn(this->m_handle);
    }

    // free the library
#ifdef WIN32
    if(m_hDllFile) FreeLibrary(m_hDllFile);
#else // assume linux
    assert(false && "linux part not yet implanted");
#endif

    // reset data
    this->resetAllMembers();
}

/**
 * Calls the \c getinertias function in the currently loaded library.
 *
 * @param inertia Data structure to be passed on to \c getinertias
 * @return Returns true if \c getinertias returns with succes, false otherwise.
 */
bool ModelSimulator::getInertias(ModelSimulator_inertias_t &inertia)
{
    if(this->m_handle && this->m_getinertias_fn) {
        if( this->m_getinertias_fn(this->m_handle, reinterpret_cast<uint64_t>(&inertia)) == 0 ) {
            // if here, then success
            return true;
        }
    }
    // if here, then failed
    return false;
}

/**
 * Calls the \c setstepsize function in the currently loaded library.
 *
 * @param stepSize Stepsize value to pass on the \c setstepsize.
 * @return Returns true if \c setstepsize returns with success, false otherwise.
 */
bool ModelSimulator::setStepSize(float stepSize)
{
    if(this->m_handle && this->m_setstepsize_fn) {
        if( this->m_setstepsize_fn(this->m_handle, stepSize) == 0 ) {
            // if here, then success
            return true;
        }
    }
    // if here, then failed
    return false;
}

/**
 * Calls the \c step function in the currently loaded library
 *
 * @param environment ModelSimulator_environment_t structure to be passed on to \c step.
 * @param forces ModelSimulator_forces_t structure to be passed on to \c step.
 * @return Returns true if \c step returns with success, false otherwise.
 */
bool ModelSimulator::step(const ModelSimulator_environment_t &environment, ModelSimulator_forces_t &forces)
{
    if(this->m_handle && this->m_step_fn) {
        if( this->m_step_fn( this->m_handle, reinterpret_cast<uint64_t>(&environment) , reinterpret_cast<uint64_t>(&forces) ) == 0 ) {
            // if here, then success
            return true;
        }
    }
    // if here, then failed
    return false;
}


